/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.moderatorplugin;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.Formatter;
import java.util.List;
import java.util.Random;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.GroupLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.LayoutStyle;
import javax.swing.ToolTipManager;
import javax.swing.TransferHandler;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import com.pearreview.ui.BottomButtonPanel;
import com.pearreview.ui.HeadingLabel;
import com.pearreview.ui.PearPanel;
import com.pearreview.ui.tree.*;
import com.pearreview.application.Messages;
import com.pearreview.application.PluginAdapter;
import com.pearreview.application.SpringGlassPane;
import com.pearreview.util.XMLFileFilter;
import com.pearreview.util.review.Aspect;
import com.pearreview.util.review.Catalog;
import com.pearreview.util.review.IdList;
import com.pearreview.util.review.io.CatalogWriter;
import com.pearreview.util.review.io.XMLCatalogWriter;

public class AspectsPanel extends PearPanel implements ModeratorPanel {
	private static final long serialVersionUID = 259717333835346755L;
	
	private File workspace;
	
	private JTree catalogsTree;
	private JTree aspectsTree;
	
	private JPopupMenu aspectsPopupMenu;
	private JPopupMenu catalogsPopupMenu;
	
	private Action deleteAction = new DeleteAction();
	private Action addAction = new AddAction();
	private Action addAspectAction = new AddAspectAction();
	private Action addCategoryAction = new AddCategoryAction();
	private Action refreshAction = new RefreshAction();
	private Action applyAction = new SaveAction();
	
	private SimpleChangeListener changeListener = null;
	
	private CatalogTreeModel aspectsTreeModel;
	
	private BottomControlPanel bottomControlPanel;
	
	private JTree dragTree = null;
	
	private PluginAdapter pluginAdapter;
	
	private BottomButtonPanel bottom;
	
	public AspectsPanel(PluginAdapter pa, File workspace) {
		this(pa, workspace, createEmptyCatalogRootModel());
	}
	
	public AspectsPanel(PluginAdapter pa, File workspace, 
			CatalogTreeModel model) {
		pluginAdapter = pa;
		aspectsTreeModel = model;
		this.workspace = workspace;
		
		initComponents();
		
		/* read catalogs */	
		readCatalogs();
		catalogsTree.setCellRenderer(new CatalogTreeCellRenderer());
		catalogsTree.setRowHeight(0);

		aspectsTree.setModel(aspectsTreeModel);
		aspectsTree.setCellRenderer(new CatalogTreeCellRenderer());
		aspectsTree.setRowHeight(0);
		
		aspectsTree.addMouseListener(new AspectsPopupMouseAdapter());
		catalogsTree.addMouseListener(new CatalogsPopupMouseAdapter());
		
		/* DnD */
		catalogsTree.setDragEnabled(true);
		aspectsTree.setDragEnabled(true);
		
		MouseListener ml = new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				if(e.getSource() instanceof JTree) {
					dragTree = (JTree)e.getSource();
				}
			}
		};
		
		catalogsTree.addMouseListener(ml);
		aspectsTree.addMouseListener(ml);
		
		catalogsTree.getSelectionModel()
				.addTreeSelectionListener(new CatalogsSelectionListener());
		aspectsTree.getSelectionModel()
				.addTreeSelectionListener(new AspectsSelectionListener());
		
		catalogsTree.setDropTarget(new ExpandPathDropTarget(catalogsTree, 
				TransferHandler.NONE, null) {
			private static final long serialVersionUID = 8290688458894992214L;

			@Override
			public void dragOver(DropTargetDragEvent dtde) {
				super.dragOver(dtde);
				dtde.acceptDrag(TransferHandler.NONE);
			}
		});
		
		DropTargetListener dtl = new TreeDropTargetAdapter();
		
		aspectsTree.setDropTarget(new ExpandPathDropTarget(aspectsTree, 
				TransferHandler.COPY_OR_MOVE, dtl){
			private static final long serialVersionUID = 3137854954016945031L;
			
			@Override
			public void dragOver(DropTargetDragEvent dtde) {
				super.dragOver(dtde);
				
				Point p = dtde.getLocation();
				TreePath targetPath = 
						aspectsTree.getPathForLocation(p.x, p.y);
				
				if(dragTree == catalogsTree) {
					if(!canMoveFromCatalogsTree(targetPath))
						dtde.acceptDrag(TransferHandler.NONE);
				} else if(dragTree == aspectsTree) {
					TreePath[] sourcePaths = aspectsTree.getSelectionPaths();
					sourcePaths = 
							CatalogTreeModel.removeLowerNodes(sourcePaths);
					if(!aspectsTreeModel.canMoveNodes(sourcePaths, targetPath)
							&& !canMove(targetPath, sourcePaths))
						dtde.acceptDrag(TransferHandler.NONE);
				} else {
					dtde.acceptDrag(TransferHandler.NONE);
				}
			}
		});
		
		/* select roots */
		catalogsTree.setSelectionPath(
				new TreePath(catalogsTree.getModel().getRoot()));
		aspectsTree.setSelectionPath(
				new TreePath(aspectsTree.getModel().getRoot()));
	}
	
	private void initComponents() {
		initBottomControlPanel();
		
		setLayout(new BorderLayout());
		
		Component headingLabel = new HeadingLabel(
				Messages.getString("PearReview.Moderator.tabs.aspects"));
		add(headingLabel, BorderLayout.NORTH);
		
		aspectsPopupMenu = new JPopupMenu();
		catalogsPopupMenu = new JPopupMenu();
		
		aspectsTree = new JTree();
		KeyStroke deleteKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 
				0);
		aspectsTree.getInputMap().put(deleteKeyStroke, "removeNodes");
		aspectsTree.getActionMap().put("removeNodes", deleteAction);
		aspectsTree.add(aspectsPopupMenu);
		aspectsTree.setBorder(new EmptyBorder(10, 10, 10, 10));
		ToolTipManager.sharedInstance().registerComponent(aspectsTree);
		JScrollPane aspectsScrollPane = new JScrollPane(aspectsTree);
		aspectsScrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
		
		catalogsTree = new JTree();
		catalogsTree.add(catalogsPopupMenu);
		catalogsTree.setBorder(new EmptyBorder(10, 10, 10, 10));
		ToolTipManager.sharedInstance().registerComponent(catalogsTree);
		JScrollPane catalogsScrollPane = new JScrollPane(catalogsTree);
		catalogsScrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
		
		
		JPanel formPanel = new JPanel();
		formPanel.setOpaque(false);
		
		
		JSplitPane splitPane = new JSplitPane();
		splitPane.setResizeWeight(0.5);
		splitPane.setContinuousLayout(true);
		splitPane.setBackground(new Color(0, true));
		BasicSplitPaneDivider dividerContainer = 
				(BasicSplitPaneDivider) splitPane.getComponent(2);
		splitPane.setRightComponent(aspectsScrollPane);
		splitPane.setLeftComponent(catalogsScrollPane);
		
		JPanel buttonsPanel = new JPanel();
		JButton addButton = new JButton();
		JButton subButton = new JButton();
		addButton.setAction(addAction);
		subButton.setAction(deleteAction);
		addButton.setText(null);
		subButton.setText(null);
		addButton.setToolTipText(getLocalizedString("add"));
		subButton.setToolTipText(getLocalizedString("remove"));
		addButton.setEnabled(false);
		subButton.setEnabled(false);
		addButton.setOpaque(false);
		subButton.setOpaque(false);
		buttonsPanel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
		addButton.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
		subButton.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
		buttonsPanel.setBackground(new Color(0, true));
		buttonsPanel.setOpaque(false);
		GroupLayout btnLayout = new GroupLayout(buttonsPanel);
		buttonsPanel.setLayout(btnLayout);
		btnLayout.setHorizontalGroup(
				btnLayout.createParallelGroup()
				.addGroup(btnLayout.createSequentialGroup()
					.addGroup(btnLayout.createParallelGroup()
						.addComponent(addButton)
						.addComponent(subButton)))
		);
		btnLayout.setVerticalGroup(
				btnLayout.createParallelGroup()
				.addGroup(btnLayout.createSequentialGroup()
					.addComponent(addButton)
					.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
					.addComponent(subButton))
		);
		
		dividerContainer.setBackground(new Color(0, true));
		
		GridBagLayout gbl = new GridBagLayout();
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.anchor = GridBagConstraints.CENTER;
		dividerContainer.setLayout(gbl);
		gbl.setConstraints(buttonsPanel, gbc);
		dividerContainer.add(buttonsPanel);
		int size = buttonsPanel.getPreferredSize().width;
		size += 6; // gaps
		dividerContainer.setDividerSize(size);
		

		
		GroupLayout layout = new GroupLayout(formPanel);
		formPanel.setLayout(layout);
		layout.setHorizontalGroup(
			layout.createParallelGroup()
				.addGroup(layout.createSequentialGroup()
					.addContainerGap()
					.addComponent(splitPane, 
							GroupLayout.DEFAULT_SIZE, 
							0, 
							Short.MAX_VALUE)
					.addContainerGap())
				.addComponent(bottomControlPanel)
		);
		layout.setVerticalGroup(
			layout.createParallelGroup()
				.addGroup(layout.createSequentialGroup()
					.addContainerGap()
					.addComponent(splitPane, 
							GroupLayout.DEFAULT_SIZE, 
							0, 
							Short.MAX_VALUE)
					.addContainerGap()
					.addComponent(bottomControlPanel,
							GroupLayout.PREFERRED_SIZE,
							GroupLayout.PREFERRED_SIZE,
							GroupLayout.PREFERRED_SIZE))
		);
		
		add(formPanel, BorderLayout.CENTER);
		
		bottom = new BottomButtonPanel();
		add(bottom, BorderLayout.SOUTH);
	}
	
	private void initBottomControlPanel() {
		OpenManagerAction openCatalogManager = new OpenManagerAction();
		SaveAsCatalogAction saveAsCatalogAction = new SaveAsCatalogAction();
		
		bottomControlPanel = new BottomControlPanel(
				openCatalogManager, 
				addCategoryAction, 
				addAspectAction,
				saveAsCatalogAction,
				refreshAction,
				applyAction);
	}
	
	private static CatalogTreeModel createEmptyCatalogRootModel() {
		CatalogTreeModel ctm = new CatalogTreeModel();
		ctm.createEmptyAspectsNode(getLocalizedString("root.aspects"));
		return ctm;
	}
	
	/**
	 * Sets aspects 
	 * @param aspects
	 */
	public void setAspects(IdList<Aspect> aspects) {
		aspectsTreeModel.setAspects(aspects, 
				getLocalizedString("root.aspects"));
		
		aspectsTree.setSelectionPath(
				new TreePath(aspectsTree.getModel().getRoot()));
	}
	
	@Override
	public boolean isValidForm() {
		return aspectsTreeModel.getRoot().getChildCount() > 0;
	}
	
	/**
	 * Returns review aspects
	 * @return review aspects
	 */
	public IdList<Aspect> getAspects() {
		return aspectsTreeModel.getAspects();
	}
	
	/**
	 * Returns aspects tree model
	 * @return aspects tree model
	 */
	public CatalogTreeModel getAspectsModel() {
		return aspectsTreeModel;
	}
	
	/**
	 * initialize catalogs tree model
	 */
	public void readCatalogs() {
		File[] catalogFiles = CatalogManager.getCatalogFiles(workspace);
		
		/* read catalogs */
		Catalog[] catalogs = new Catalog[catalogFiles.length];
		String[] catalogNames = new String[catalogFiles.length];
		for(int i = 0 ; i < catalogFiles.length ; i++) {
			try {
				catalogs[i] = CatalogManager.getCatalog(catalogFiles[i]);
				catalogNames[i] = 
						CatalogManager.getCatalogName(catalogFiles[i]);
			} catch(Throwable e) {
				// TODO error handling
				e.printStackTrace();
				
				catalogFiles[i] = null;
				catalogs[i] = null;
				catalogNames[i] = null;
			}
		}
		
		CatalogTreeModel treeModel = new CatalogTreeModel();
		treeModel.createEmptyCatalogRoot(getLocalizedString("root.catalogs"));
		TreePath rootPath = new TreePath(treeModel.getRoot());
		
		for(int i = 0 ; i < catalogFiles.length ; i++) {
			if(catalogFiles[i] == null)
				continue;
			
			treeModel.addCatalogNode(rootPath, 
					catalogs[i], 
					catalogNames[i]);
		}
		
		catalogsTree.setModel(treeModel);
	}
	
	/**
	 * Action for adding a new category to catalog/aspect-list
	 *
	 */
	private class AddCategoryAction extends AbstractAction {
		private static final long serialVersionUID = 1882404362675390321L;
		
		public AddCategoryAction() {
			setEnabled(false);
			
			Icon largeIcon = getRessourceIcon("folder-new.png");
			Icon smallIcon = getRessourceIcon("folder-new_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(AbstractAction.NAME, 
					getLocalizedString("add.category"));
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			final TreePath[] selectedPaths = aspectsTree.getSelectionPaths();
			
			if(selectedPaths == null)
				return;
			
			final TreePath[] addedPaths = new TreePath[selectedPaths.length];
			
			String categoryName = getLocalizedString("add.category");
			String aspectName = getLocalizedString("add.aspect");
			String aspectDescription = getLocalizedString("new.description");
			
			String categoryNameIter;
			
			for(int i = 0 ; i < selectedPaths.length ; i++) {
				if(!canInsertCategory(selectedPaths[i], null))
					return;
				
				int index = -1;
				
				Object node = selectedPaths[i].getLastPathComponent();
				if(node instanceof AspectTreeNode) {
					selectedPaths[i] = selectedPaths[i].getParentPath();
					TreeNode tn = (TreeNode)node;
					index = tn.getParent().getIndex(tn);
				}
				
				node = selectedPaths[i].getLastPathComponent();
				if(node instanceof CategoryTreeNode) {
					selectedPaths[i] = selectedPaths[i].getParentPath();
					TreeNode tn = (TreeNode)node;
					index = tn.getParent().getIndex(tn) + 1;
				}
				
				int j = 1;
				categoryNameIter = categoryName;
				while(!canInsertCategory(selectedPaths[i], categoryNameIter)) {
					categoryNameIter = categoryName + " " + j;
					j++;
					if(j == Short.MAX_VALUE) {
						Random rand = new Random();
						categoryNameIter = categoryName + " " +
								-rand.nextInt(Integer.MAX_VALUE);
						break;
					}
				}
				
				addedPaths[i] = aspectsTreeModel.insertCategoryNode(
						selectedPaths[i],
						categoryNameIter,
						index);
				/* and adds aspects */
				aspectsTreeModel.addAspectNode(addedPaths[i], 
						getNextNewAspect(addedPaths[i], aspectName), 
						aspectDescription);
				
				aspectsTree.expandPath(addedPaths[i]);
			}
			
			aspectsTree.setSelectionPaths(addedPaths);
		}
	}
	
	private String getNextNewAspect(TreePath path, String name) {
		String aspectNameIter;
		if(!canInsertAspect(path, null))
			return null;
		
		short j = 1;
		aspectNameIter = name;
		while(!canInsertAspect(path, aspectNameIter)) {
			aspectNameIter = name + " " + j;
			j++;
			if(j == Short.MAX_VALUE) {
				Random rand = new Random();
				aspectNameIter = name + " " + -rand.nextInt(Integer.MAX_VALUE);
				break;
			}
		}
		
		return aspectNameIter;
	}

	/**
	 * Action for adding a new aspect to category/catalog/aspect-list
	 *
	 */
	private class AddAspectAction extends AbstractAction {
		private static final long serialVersionUID = 3792034004790481840L;
		public AddAspectAction() {
			setEnabled(false);
			
			Icon largeIcon = getRessourceIcon("document-new.png");
			Icon smallIcon = getRessourceIcon("document-new_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(AbstractAction.NAME, 
					getLocalizedString("add.aspect"));
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			final TreePath[] selectedPaths = aspectsTree.getSelectionPaths();
			if(selectedPaths == null)
				return;
			
			final TreePath[] addedPaths = new TreePath[selectedPaths.length];
			
			String aspectName = getLocalizedString("add.aspect");
			String aspectDescription = getLocalizedString("new.description");
			
			for(int i = 0 ; i < selectedPaths.length ; i++) {
				if(!canInsertAspect(selectedPaths[i], null))
					return;
				
				int index = -1;
				Object node = selectedPaths[i].getLastPathComponent();
				
				if(node instanceof AspectTreeNode) {
					selectedPaths[i] = selectedPaths[i].getParentPath();
					TreeNode tn = (TreeNode)node;
					index = tn.getParent().getIndex(tn) + 1;
				}
					
				addedPaths[i] = aspectsTreeModel.insertAspectNode(
						selectedPaths[i], 
						getNextNewAspect(selectedPaths[i], aspectName), 
						aspectDescription,
						index);
			}
			
			aspectsTree.setSelectionPaths(addedPaths);
		}
	}
	
	private boolean canInsertAspect(TreePath path, String name) {
		if(path == null || path.getPathCount() <= 0)
			return false;
		
		Object node = path.getLastPathComponent();
		
		if(!(node instanceof CategoryTreeNode) 
				&& !(node instanceof CatalogTreeNode)
				&& !(node instanceof AspectsTreeNode)
				&& !(node instanceof CatalogTreeNode)
				&& !(node instanceof AspectTreeNode))
			return false;
		
		if(name == null)
			return true;
		
		TreeNode tn = (TreeNode) node;
		int count = tn.getChildCount();
		
		for(int i = 0 ; i < count ; i++) {
			TreeNode child = tn.getChildAt(i);
			if(child instanceof AspectTreeNode) {
				AspectTreeNode atn = (AspectTreeNode) child;
				if(name.equals(atn.getAspect().getDirective()))
					return false;
			}
		}
		
		return true;
	}
	
	private boolean canInsertAspect(TreePath[] paths, String name) {
		if(paths == null || paths.length != 1)
			return false;
		
		return canInsertAspect(paths[0], name);
	}
	
	private boolean canInsertCategory(TreePath path, String category) {
		if(path == null || path.getPathCount() <= 0)
			return false;
		
		Object node = path.getLastPathComponent();

		if(node instanceof CatalogTreeNode) {
			if(category == null)
				return true;
			CatalogTreeNode ctn = (CatalogTreeNode)node;
			
			Enumeration<?> e = ctn.children();
			Object cNode;
			String cNodeName;
			while(e.hasMoreElements()) {
				cNode = e.nextElement();
				if(cNode instanceof CategoryTreeNode) {
					cNodeName = ((CategoryTreeNode) cNode).getName();
					if(category.equals(cNodeName))
						return false;
				}
			}
			
			return true;
		} else if(node instanceof AspectsTreeNode) {
			if(category == null)
				return true;
			AspectsTreeNode atn = (AspectsTreeNode)node;
			
			Enumeration<?> e = atn.children();
			Object cNode;
			String cNodeName;
			while(e.hasMoreElements()) {
				cNode = e.nextElement();
				if(cNode instanceof CategoryTreeNode) {
					cNodeName = ((CategoryTreeNode) cNode).getName();
					if(category.equals(cNodeName))
						return false;
				}
			}
			
			return true;
		} else if(category == null && (node instanceof CategoryTreeNode 
				|| node instanceof AspectTreeNode 
				|| node instanceof CategoryTreeNode)) {
			return true;
		}
		
		return false;
	}
	
	private boolean canInsertCategory(TreePath[] paths, String category) {
		if(paths == null || paths.length != 1)
			return false;
		
		return canInsertCategory(paths[0], category);
	}
	
	/**
	 * Checks if path can be removed
	 * @param path path to check
	 * @return <code>true</code> if path can be removed, otherwise
	 * <code>false</code>
	 */
	private boolean canRemove(TreePath path) {
		if(path == null || path.getPathCount() <= 0)
			return false;
		return true;
	}
	
	/**
	 * Checks if path can be removed
	 * @param path path to check
	 * @return <code>true</code> if path can be removed, otherwise
	 * <code>false</code>
	 */
	private boolean canRemove(TreePath[] paths) {
		if(paths == null || paths.length == 0)
			return false;
		
		paths = CatalogTreeModel.removeLowerNodes(paths);
			
		if(paths == null || paths.length == 0)
			return false;
		
		for(int i = 0 ; i < paths.length ; i++) {
			if(!canRemove(paths[i]))
				return false;
		}
		
		return true;
	}
	
	private boolean canMove(TreePath targetPath, TreePath[] sourcePaths) {
		if(sourcePaths == null || sourcePaths.length == 0)
			return false;
		
		sourcePaths = CatalogTreeModel.removeLowerNodes(sourcePaths);
			
		if(sourcePaths == null || sourcePaths.length == 0)
			return false;
		
		for(int i = 0 ; i < sourcePaths.length ; i++) {
			if(!canMove(targetPath, sourcePaths[i]))
				return false;
		}
		
		return true;
	}
	
	private boolean canMove(TreePath targetPath, TreePath sourcePath) {
		if(targetPath == null || sourcePath == null)
			return false;
		
		Object sourceObj = sourcePath.getLastPathComponent();
		Object targentObj = targetPath.getLastPathComponent();
		
		if(!(sourceObj instanceof TreeNode) 
				|| !(targentObj instanceof TreeNode)) {
			return false;
		}
		
		if(targetPath.equals(sourcePath))
			return true;
		
		if(!canRemove(sourcePath))
			return false;
		
		TreeNode sourceNode = (TreeNode)sourceObj;
		
		if(sourceNode.getParent() == null)
			return false;
		
		if(sourcePath.isDescendant(targetPath))
			return false;
		if(sourceObj instanceof CategoryTreeNode 
				&& targentObj instanceof AspectTreeNode)
			return false;
		if(sourceNode instanceof AspectsRootTreeNode)
			return false;
		if(sourceNode instanceof CategoryTreeNode)
			return canInsertCategory(targetPath, null);
		if(sourceNode instanceof AspectTreeNode)
			return canInsertAspect(targetPath, null);
		
		return false;
	}
	
	private class DeleteAction extends AbstractAction {
		private static final long serialVersionUID = 5686085243839917076L;
		public DeleteAction() {
			setEnabled(false);
			
			Icon largeIcon = getRessourceIcon("edit-delete.png");
			Icon smallIcon = getRessourceIcon("edit-delete_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(Action.NAME, getLocalizedString("remove"));
			
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			TreePath[] selectedPaths = aspectsTree.getSelectionPaths();
			
			if(selectedPaths == null)
				return;
			
			selectedPaths = CatalogTreeModel.removeLowerNodes(selectedPaths);
			
			if(selectedPaths == null)
				return;
			
			for(TreePath cPath : selectedPaths) {
				removeNode(cPath);
			}
		}
	}
	
	private void removeNode(TreePath path) {
		Object node = path.getLastPathComponent();
		
		if(node instanceof AspectsTreeNode) {
			aspectsTreeModel.removeChildren(path);
			
			if(changeListener != null)
				changeListener.clearedAspects();
		} else if(node instanceof CategoryTreeNode) {
			aspectsTreeModel.removeNode(path);
			
			CategoryTreeNode ctn = (CategoryTreeNode)node;
			
			if(changeListener != null)
				changeListener.removedCategory(ctn.getName());
		} else if(node instanceof AspectTreeNode) {
			TreeNode parent = ((TreeNode)node).getParent();
			aspectsTreeModel.removeNode(path);
			
			AspectTreeNode atn = (AspectTreeNode)node;
			
			if(parent instanceof CategoryTreeNode 
					&& parent.getChildCount() == 0) {
				aspectsTreeModel.removeNode(path.getParentPath());
				CategoryTreeNode ctn = (CategoryTreeNode) parent;
				if(changeListener != null)
					changeListener.removedCategory(ctn.getName());
			} else if(changeListener != null)
				changeListener.removedAspect(atn.getAspect());
		}
	}
	
	private class AddAction extends AbstractAction implements Serializable {
		private static final long serialVersionUID = -1240120114768257963L;
		public AddAction() {
			setEnabled(false);
			
			Icon largeIcon = getRessourceIcon("go-next.png");
			Icon smallIcon = getRessourceIcon("go-next_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(AbstractAction.NAME, getLocalizedString("add"));
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			moveFromCatalogsTree(null);
		}
	}
	
	private class AspectsPopupMouseAdapter extends MouseAdapter
			implements Serializable {
		private static final long serialVersionUID = 4305338436782945733L;
		
		private void popup(MouseEvent e) {
			aspectsPopupMenu.removeAll();
			int x = e.getX();
			int y = e.getY();
			TreePath clickedPath = aspectsTree.getPathForLocation(x, y);
			TreePath[] selectedPaths = aspectsTree.getSelectionPaths();
			
			if(clickedPath == null)
				return;
			
			if(selectedPaths != null 
					&& Arrays.asList(selectedPaths).contains(clickedPath) 
					&& selectedPaths.length > 1) {
					
			} else {
				aspectsTree.setSelectionPath(clickedPath);
				
				if(canInsertAspect(clickedPath, null))
					aspectsPopupMenu.add(getAddAspectItem());
				
				if(canInsertCategory(clickedPath, null))
					aspectsPopupMenu.add(getAddCategoryItem());
				
				if(canRemove(clickedPath))
					aspectsPopupMenu.add(getRemoveItem());
			}
			if(aspectsPopupMenu.getComponentCount() > 0)
				aspectsPopupMenu.show(aspectsTree, x, y);
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			if (!e.isPopupTrigger())
				return;
			
			popup(e);
		}
		
		@Override
		public void mousePressed(MouseEvent e) {
			if (!e.isPopupTrigger())
				return;
			
			popup(e);
		}
		
		private JMenuItem getAddCategoryItem() {
			JMenuItem menuItem = new JMenuItem();
			menuItem.setAction(addCategoryAction);
			return menuItem;
		}
		
		private JMenuItem getAddAspectItem() {
			JMenuItem menuItem = new JMenuItem();
			menuItem.setAction(addAspectAction);
			return menuItem;
		}
		
		private JMenuItem getRemoveItem() {
			JMenuItem menuItem = new JMenuItem();
			menuItem.setAction(deleteAction);
			return menuItem;
		}
	}
	
	private class CatalogsPopupMouseAdapter extends MouseAdapter
			implements Serializable {
		private static final long serialVersionUID = -6246756099515940008L;
		
		private void popup(MouseEvent e) {
			catalogsPopupMenu.removeAll();
			int x = e.getX();
			int y = e.getY();
			TreePath clickedPath = catalogsTree.getPathForLocation(x, y);
			TreePath[] selectedPaths = catalogsTree.getSelectionPaths();
			
			if(clickedPath == null)
				return;
			
			if(selectedPaths != null 
					&& Arrays.asList(selectedPaths).contains(clickedPath) 
					&& selectedPaths.length > 1) {
				if(canMoveFromCatalogsTree(null)) {
					JMenuItem item = new JMenuItem();
					item.setAction(addAction);
					catalogsPopupMenu.add(item);
				}
			} else {
				catalogsTree.setSelectionPath(clickedPath);
				
				if(!(clickedPath.getLastPathComponent() 
						instanceof CatalogRootTreeNode)) {
					JMenuItem item = new JMenuItem();
					item.setAction(addAction);
					catalogsPopupMenu.add(item);
				} else {
					JMenuItem item = new JMenuItem();
					item.setAction(refreshAction);
					catalogsPopupMenu.add(item);
				}
			}
			if(catalogsPopupMenu.getComponentCount() > 0)
				catalogsPopupMenu.show(catalogsTree, x, y);
		}
		
		@Override
		public void mouseReleased(MouseEvent e) {
			if (!e.isPopupTrigger())
				return;
			
			popup(e);
		}
		
		@Override
		public void mousePressed(MouseEvent e) {
			if (!e.isPopupTrigger())
				return;
			
			popup(e);
		}
	}
	
	private class CatalogsSelectionListener implements TreeSelectionListener, 
			Serializable {
		private static final long serialVersionUID = 5068108962116289427L;

		@Override
		public void valueChanged(TreeSelectionEvent e) {
			
			TreePath[] paths = catalogsTree.getSelectionPaths();
				
			if(paths == null || paths.length == 0) {
				addAction.setEnabled(false);
				return;
			}
			
			for(TreePath path : paths) {
				if(path.getLastPathComponent() 
						instanceof CatalogRootTreeNode) {
					addAction.setEnabled(false);
					return;
				}
			}
			
			addAction.setEnabled(true);
		}
	}
	
	private class AspectsSelectionListener implements TreeSelectionListener, 
			Serializable {
		private static final long serialVersionUID = -8400912997163881311L;
		@Override
		public void valueChanged(TreeSelectionEvent e) {
			selectionChanged();
		}
	}
	
	private void selectionChanged() {
		TreePath[] selectedPaths = aspectsTree.getSelectionPaths();
		
		deleteAction.setEnabled(canRemove(selectedPaths));
		addAspectAction.setEnabled(canInsertAspect(selectedPaths, null));
		addCategoryAction.setEnabled(canInsertCategory(selectedPaths, null));
		
		Object node = null;
		TreePath nodePath = null;
		if(selectedPaths != null && selectedPaths.length == 1) {
			nodePath = selectedPaths[0];
			node = nodePath.getLastPathComponent();
		}
		
		//show forms
		
		if (node instanceof AspectTreeNode) {
			
			AspectTreeNode selectedNode = (AspectTreeNode)node;
			Aspect aspect = selectedNode.getAspect();
			
			TreeNode parentNode = selectedNode.getParent();
			if(parentNode instanceof CategoryTreeNode) {
				CategoryTreeNode ctn = (CategoryTreeNode)parentNode;
				aspect.setCategory(ctn.getName());
				parentNode = parentNode.getParent();
			} else if(parentNode instanceof AspectsTreeNode) {
				aspect.setCategory("");
			}
			
			if(AspectsPanel.this.canRemove(nodePath)) {
				List<String> categoryList = new ArrayList<String>();
				int childCount = parentNode.getChildCount();
				for(int i = 0 ; i < childCount ; i++) {
					TreeNode tn = parentNode.getChildAt(i);
					if(!(tn instanceof CategoryTreeNode))
						continue;
					categoryList.add(((CategoryTreeNode)tn).getName());
				}
				
				String[] categories = new String[categoryList.size() + 1];
				categoryList.toArray(categories);
				categories[categories.length - 1] = "";
				
				bottomControlPanel.showAspectForm(aspect,
					categories);
			} else {
				bottomControlPanel.showAspectForm(aspect);
			}
			
		} else if (node instanceof CategoryTreeNode) {
			CategoryTreeNode selectedCategory = (CategoryTreeNode)node;
			bottomControlPanel.showCategoryForm(
					selectedCategory.getName());
		} else {
			bottomControlPanel.showNoForm();
		}
	}
	
	/**
	 * Gets category path, if category already exists new will be created.
	 * @param category
	 * @return category node
	 */
	private TreePath getAspectsCategory(String category) {
		TreeNode atn = (TreeNode)aspectsTreeModel.getRoot();
		
		TreePath path = new TreePath(atn);
		
		Enumeration<?> children = atn.children();
		while(children.hasMoreElements()) {
			TreeNode child = (TreeNode)children.nextElement();
			if(child instanceof CategoryTreeNode) {
				if(((CategoryTreeNode)child).getName()
						.equals(category)) {
					return path.pathByAddingChild(child);
				}
			}
		}
		
		return aspectsTreeModel.addCategoryNode(path, category);
	}
	
	private TreePath moveAspectFromCatalogsTree(TreePath aspectTreePath,
			TreePath targetPath) {
		TreePath addPath = null;
		
		TreeNode sourceNode = 
				(TreeNode)aspectTreePath.getLastPathComponent();
		AspectTreeNode atn = (AspectTreeNode)sourceNode;
		Aspect aspect = atn.getAspect();
		
		
		if(targetPath == null) {
			addPath = new TreePath(aspectsTreeModel.getRoot());
			if(atn.getParent() instanceof CategoryTreeNode) {
				CategoryTreeNode ctn = (CategoryTreeNode)atn.getParent();
				addPath = getAspectsCategory(ctn.getName());
			}
		}
		else {
			addPath = targetPath;
		}
		
		return aspectsTreeModel.addAspectNode(addPath, 
				aspect.getDirective(), 
				aspect.getDescription());
	}
	
	private TreePath moveCategoryFromCatalogsTree(TreePath categoryTreePath) {
		boolean expanded = catalogsTree.isExpanded(categoryTreePath);
		
		Object node = categoryTreePath.getLastPathComponent();
		
		if(!(node instanceof CategoryTreeNode))
			throw new IllegalArgumentException();
		
		CategoryTreeNode ctn = (CategoryTreeNode)node;
		
		String category = ctn.getName();
		
		TreePath aspectsCategoryPath = getAspectsCategory(category);
		
		/* get children */
		Enumeration<?> e = ctn.children();
		while(e.hasMoreElements()) {
			TreePath childPath = categoryTreePath
					.pathByAddingChild(e.nextElement());
			moveAspectFromCatalogsTree(childPath, null);
		}
		
		if(expanded)
			aspectsTree.expandPath(aspectsCategoryPath);
		
		return aspectsCategoryPath;
	}
	
	private TreePath[] moveCatalogFromCatalogsTree(TreePath catalogTreePath) {
		Object node = catalogTreePath.getLastPathComponent();
		
		if(!(node instanceof CatalogTreeNode))
			throw new IllegalArgumentException();
		
		CatalogTreeNode ctn = (CatalogTreeNode)node;
		
		int cildCount = ctn.getChildCount();
		
		TreePath[] addedPath = new TreePath[cildCount];
		
		for(int i = 0 ; i < cildCount ; i++) {
			TreePath cPath = catalogTreePath
					.pathByAddingChild(ctn.getChildAt(i));
			
			Object cNode = cPath.getLastPathComponent();
			
			if(cNode instanceof CategoryTreeNode) {
				addedPath[i] = moveCategoryFromCatalogsTree(cPath);
			} else if(cNode instanceof AspectTreeNode) {
				addedPath[i] = moveAspectFromCatalogsTree(cPath, null);
			} else {
				throw new IllegalArgumentException();
			}
		}
		
		return addedPath;
	}
	
	/**
	 * Moves selected items in catalogs tree to aspects tree model
	 * @param targetPath
	 */
	private void moveFromCatalogsTree(TreePath targetPath) {
		TreePath[] source = CatalogTreeModel
				.removeLowerNodes(catalogsTree.getSelectionPaths());
		if(source == null)
			return;
		
		for(int i = 0 ; i < source.length ; i++) {
			TreePath cPath = source[i];
			int level = CatalogTreeModel.getNodeLevel(cPath);
			
			switch(level) {
			/* aspect */
			case 4:
				moveAspectFromCatalogsTree(cPath, targetPath);
				break;
				
			/* category */
			case 3:
				moveCategoryFromCatalogsTree(cPath);
				break;
				
			/* catalog */
			case 2:
				moveCatalogFromCatalogsTree(cPath);
				break;
			}
		}
		
		/* select moved nodes - not supported now */
		//aspectsTree.setSelectionPaths(addedPaths);
		
		/* update selection */
		selectionChanged();
	}
	
	/**
	 * Checks if selected items in catalogs tree can be moved to target
	 * @param targetPath
	 * @return
	 */
	private boolean canMoveFromCatalogsTree(TreePath targetPath) {	
		Object node = null;
		if(targetPath != null) {
			node = targetPath.getLastPathComponent();
		}
		int lvl = CatalogTreeModel
				.getUpperNodeLevel(catalogsTree.getSelectionPaths());
		if(node instanceof CategoryTreeNode) {
			if(lvl != 4)
				return false;
		} else if(node instanceof AspectTreeNode) {
			return false;
		} else if(lvl == 1 || lvl == 0)
			return false;
		return true;
	}
	
	private Icon getRessourceIcon(String name) {
		return new ImageIcon(getClass().getResource("images/" + name));
	}
	
	/**
	 * Returns localized string of this component
	 * @param key key of string to localize
	 * @return localized string of this component
	 */
	private static String getLocalizedString(String key) {
		return Messages.getString("PearReview.Moderator.aspects." + key);
	}
	
	private class TreeDropTargetAdapter extends DropTargetAdapter
			implements Serializable {
		private static final long serialVersionUID = 693272052029182660L;

		@Override
		public void drop(DropTargetDropEvent dtde) {
			Point p = dtde.getLocation();
			TreePath targetPath = 
						aspectsTree.getPathForLocation(p.x, p.y);
			if(dragTree == aspectsTree) {			
				TreePath[] sourcePaths = aspectsTree.getSelectionPaths();
				
				sourcePaths = CatalogTreeModel.removeLowerNodes(sourcePaths);
				
				targetPath = aspectsTree.getClosestPathForLocation(p.x, p.y);
				if(aspectsTreeModel.canMoveNodes(sourcePaths, targetPath)) {
					TreePath paths[] = new TreePath[sourcePaths.length];
					TreePath addPath = targetPath;
					for(int i = 0 ;  i < sourcePaths.length ; i++) {
						boolean expanded = 
								aspectsTree.isExpanded(sourcePaths[i]);
						paths[i] = aspectsTreeModel.moveNode(sourcePaths[i], 
								addPath);
						addPath = paths[i];
						if(expanded)
							aspectsTree.expandPath(addPath);
					}
					aspectsTree.setSelectionPaths(paths);
					
				} else if(canMove(targetPath, sourcePaths)) {
					TreePath[] selectPaths = new TreePath[sourcePaths.length];
					for(int i = 0 ; i < sourcePaths.length ; i++) {
						selectPaths[i] = move(sourcePaths[i], targetPath);
					}
					aspectsTree.setSelectionPaths(selectPaths);
					
				/* do nothing */
				} else {
					dtde.rejectDrop();
					dtde.dropComplete(false);
					return;
				}
			} else if(dragTree == catalogsTree) {
				if(canMoveFromCatalogsTree(targetPath)) {
					moveFromCatalogsTree(targetPath);
				}
			} else {
				dtde.rejectDrop();
				dtde.dropComplete(false);
			}
		}
		
		private TreePath move(TreePath sourcePath, TreePath targetPath) {
			Object sourceNode = sourcePath.getLastPathComponent();
			Object targetNode = targetPath.getLastPathComponent();
			
			TreePath selectPath = null;
			
			if(targetNode.equals(sourceNode)) {
				selectPath = sourcePath;
			} else if(sourceNode instanceof AspectTreeNode) {
				int index = -1;
				if(targetNode instanceof AspectTreeNode) {
					TreeNode tn = (TreeNode) targetNode;
					index  = tn.getParent().getIndex(tn);
					targetPath = targetPath.getParentPath();
				}
				
				if(targetPath.equals(sourcePath.getParentPath()))
					aspectsTreeModel.removeNode(sourcePath);
				else
					removeNode(sourcePath);
				
				AspectTreeNode atn = (AspectTreeNode)sourceNode;
				Aspect aspect = atn.getAspect();
				String oldCategory = aspect.getCategory();
				selectPath = aspectsTreeModel.insertAspectNode(
						targetPath,
						aspect, 
						index);
				
				
				/* fire event */
				if(changeListener != null)
					changeListener.changedAspect(aspect, oldCategory);
			} else if(sourceNode instanceof CategoryTreeNode) {
				AspectsTreeNode atn = 
						(AspectsTreeNode) aspectsTreeModel.getRoot();
				TreePath rootTreePath = new TreePath(atn);
				
				int lastIndex = atn.getCategoryChildCount() - 1;
				Object lastNode = atn.getChildAt(lastIndex);
				
				TreePath insertPath = rootTreePath.pathByAddingChild(lastNode);
				
				boolean expanded = aspectsTree.isExpanded(sourcePath);
				
				selectPath = aspectsTreeModel.moveNode(sourcePath, insertPath);
				
				if(expanded)
					aspectsTree.expandPath(selectPath);
			}
			
			return selectPath;
		}
	}
	
	/**
	 * This action opens the catalog manager.
	 *
	 */
	class OpenManagerAction extends AbstractAction {
	
		private static final long serialVersionUID = 2265935435282823033L;
		
		public OpenManagerAction() {
			
			Icon largeIcon = getRessourceIcon("view-tree22.png");
			Icon smallIcon = getRessourceIcon("view-tree_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			this.putValue(AbstractAction.NAME, 
					getLocalizedString("open_manager"));
			
		}

		@Override
		public void actionPerformed(ActionEvent e) {

			pluginAdapter.getSpringGlassPane().setFaded(true);
			CatalogManager.showManagerDialog(AspectsPanel.this, workspace);
			pluginAdapter.getSpringGlassPane().setFaded(false);
				
			readCatalogs();
		}
	}
	
	private void setFaded(boolean fade) {
		SpringGlassPane sgp = pluginAdapter.getSpringGlassPane();
		if(sgp != null) {
			sgp.setFaded(fade);
		}
	}
	
	/**
	 * Action for saving the aspects list as catalog.
	 *
	 */
	private class SaveAsCatalogAction extends AbstractAction {
		private static final long serialVersionUID = -1222338336173838795L;
		public SaveAsCatalogAction() {
			putValue(AbstractAction.NAME, 
					getLocalizedString("save_as_catalog"));
		}
		private boolean canInsertCatalog(TreePath path, String name) {
			if(path == null || path.getPathCount() <= 0)
				return name == null ? true : false;
			
			Object node = path.getLastPathComponent();
			
			if(node instanceof CatalogRootTreeNode) {
				if(name == null)
					return true;
				CatalogRootTreeNode crtn = (CatalogRootTreeNode)node;
				
				Enumeration<?> e = crtn.children();
				Object cNode;
				String cNodeName;
				while(e.hasMoreElements()) {
					cNode = e.nextElement();
					if(cNode instanceof CatalogTreeNode) {
						cNodeName = ((CatalogTreeNode) cNode).getName();
						if(name.equals(cNodeName))
							return false;
					}
				}
				
				return true;
			} else if(name == null && (node instanceof CategoryTreeNode 
					|| node instanceof AspectTreeNode 
					|| node instanceof CatalogTreeNode
					|| node instanceof CatalogRootTreeNode)) {
				return true;
			}
			
			return false;
		}
		private String getCatalogFreeName(TreePath path, String name) {
			
			String catalogNameIter;
			if(!canInsertCatalog(path, null))
				return null;
			
			short j = 1;
			catalogNameIter = name;
			while(!canInsertCatalog(path, catalogNameIter)) {
				catalogNameIter = name + "(" + j + ")";
				j++;
				if(j == Short.MAX_VALUE) {
					Random rand = new Random();
					catalogNameIter = name + " " +
							-rand.nextInt(Integer.MAX_VALUE);
					break;
				}
			}
			
			return catalogNameIter;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			Catalog cat = new Catalog();
			DateFormat.getInstance();
			DateFormat df = new SimpleDateFormat("dd.MM.yyyy");
			
			Date now = new Date();
			
			Formatter formatter = new Formatter();
			String description = formatter.format(
					getLocalizedString("new_catalog.description"), 
					df.format(now)).toString();
			
			formatter = new Formatter();
			String name = formatter.format(
					getLocalizedString("new_catalog.name"), 
					df.format(now)).toString();
			
			name = getCatalogFreeName(
					new TreePath(catalogsTree.getModel().getRoot()), name);
			
			name += XMLFileFilter.EXTENSION;
			
			cat.setDescription(description);
			
			cat.setAspects(aspectsTreeModel.getAspects());
			
			if(cat.getAspects() == null || cat.getAspects().size() == 0)
				return;
			
			try {
				CatalogWriter cw = new XMLCatalogWriter();
				cw.write(cat, new File(workspace, name));
				
			} catch(Throwable th) {
				String errorMessage = getLocalizedString(
						"error.write");
				formatter = new Formatter();
				formatter.format(errorMessage, th.getMessage());
				errorMessage = formatter.toString();
				String title = getLocalizedString(
						"error.write.title");
				
				setFaded(true);
				JOptionPane.showMessageDialog(AspectsPanel.this, 
						errorMessage,
						title,
						JOptionPane.ERROR_MESSAGE);
				setFaded(false);
			}
			
			readCatalogs();
		}
	}
	
	/**
	 * Action for refreshing the workspace.
	 *
	 */
	private class RefreshAction extends AbstractAction {
		
		private static final long serialVersionUID = 4460790415159585968L;

		public RefreshAction() {
			Icon largeIcon = getRessourceIcon("view-refresh.png");
			Icon smallIcon = getRessourceIcon("view-refresh_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(AbstractAction.NAME, getLocalizedString("refresh"));
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			readCatalogs();
		}
	}
	
	private class SaveAction extends AbstractAction {
		private static final long serialVersionUID = -862186863653837263L;
		public SaveAction() {
			putValue(AbstractAction.NAME, 
					getLocalizedString("save"));
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			TreePath selectedPath = aspectsTree.getSelectionPath();
			
			Object node = selectedPath.getLastPathComponent();
			
			if(node instanceof AspectTreeNode) {
				AspectTreeNode atn = (AspectTreeNode)node;
				Aspect newAspect = bottomControlPanel.getAspect();
				Aspect aspect = atn.getAspect();
				
				if(newAspect.getDirective().equals("")) {
					newAspect.setDirective(aspect.getDirective());
					bottomControlPanel.setAspect(newAspect);
				}
				
				String oldCategory = aspect.getCategory();
				
				aspect.setDirective(newAspect.getDirective());
				aspect.setDescription(newAspect.getDescription());
				aspect.setCategory(newAspect.getCategory());
				
				if(aspect.getCategory().equals(oldCategory)) {
					aspectsTreeModel.editAspectNode(selectedPath, 
						aspect.getDirective(), 
						aspect.getDescription());
					
					/* fire event */
					if(changeListener != null)
						changeListener.changedAspect(aspect);
				} else {
					/* remove */
					TreePath addPath = 
							new TreePath(aspectsTreeModel.getRoot());
					aspectsTreeModel.removeNode(selectedPath);
					
					/* add again */
					if(aspect.getCategory() != null 
							&& !aspect.getCategory().equals(""))						
						addPath = getAspectsCategory(aspect.getCategory());
					TreePath aspectPath = aspectsTreeModel.addAspectNode(
							addPath, aspect);
					
					/* select again */
					aspectsTree.setSelectionPath(aspectPath);
					
					/* fire event */
					if(changeListener != null)
						changeListener.changedAspect(aspect, oldCategory);
				}
				
			} else if(node instanceof CategoryTreeNode) {
				CategoryTreeNode ctn = (CategoryTreeNode)node;
				String oldCategory = ctn.getName();
				String newCategory = bottomControlPanel.getCategory();
				
				if(newCategory == null || newCategory.equals("")) {
					bottomControlPanel.setCategory(oldCategory);
					return;
				}
				
				if(oldCategory.equals(newCategory))
					return;
				
				TreeNode parentNode = ctn.getParent();
				int childCount = parentNode.getChildCount();
				for(int i = 0 ; i < childCount ; i++) {
					TreeNode tn = parentNode.getChildAt(i);
					if(!(tn instanceof CategoryTreeNode))
						continue;
					
					CategoryTreeNode ctnOther = (CategoryTreeNode)tn;
					
					if(ctnOther.getName().equals(newCategory)) {
						String errorMessage = getLocalizedString(
								"error.category_exists");
						Formatter formatter = new Formatter();
						formatter.format(errorMessage, oldCategory);
						errorMessage = formatter.toString();
						String title = getLocalizedString(
								"error.category_exists.title");
						
						setFaded(true);
						JOptionPane.showMessageDialog(AspectsPanel.this, 
								errorMessage,
								title,
								JOptionPane.ERROR_MESSAGE);
						setFaded(false);
						return;
					}
				}
				
				aspectsTreeModel.editCategoryNode(selectedPath, newCategory);
				
				/* fire event */
				if(changeListener != null)
					changeListener.changedCategory(oldCategory, newCategory);
			}
		}
	}
	
	public void setSimpleChangeListener(SimpleChangeListener ascl) {
		changeListener = ascl;
	}
	
	public static interface SimpleChangeListener {
		/**
		 * Category removed with all contained aspects
		 * @param category
		 */
		public void removedCategory(String category);
		/**
		 * Aspect removed
		 * @param aspect
		 */
		public void clearedAspects();
		/**
		 * Aspects cleared
		 * @param aspect
		 */
		public void removedAspect(Aspect aspect);
		/**
		 * Category name has been changed
		 * @param oldCategory
		 * @param newCategory
		 */
		public void changedCategory(String oldCategory, String newCategory);
		/**
		 * Aspect has been changed category is the same
		 * @param oldAspect
		 */
		public void changedAspect(Aspect aspect);
		/**
		 * Aspect has been changed and the category too
		 * @param oldAspect
		 * @param oldCategory
		 */
		public void changedAspect(Aspect aspect, String oldCategory);
	}

	@Override
	public String getHint() {
		return Messages.getString("PearReview.hint.moderator.aspects");
	}

	@Override
	public String getErrorMessage() {
		return Messages.getString("PearReview.Moderator.tabs.aspects_error");
	}

	@Override
	public void setNextViewAction(Action action) {
		bottom.setNextViewAction(action);
	}

	@Override
	public void setPreviousViewAction(Action action) {
		bottom.setPreviousViewAction(action);
	}
}
